<script setup lang="ts">
/**
 * @component CustomSelect
 * @description 这是一个基于a-select的下拉二次封装的组件，支持分页，支持查询
 *
 * @props 其他参数可直接参考a-select 的参数
 * @props {Function} getOptions - (info: GOLBAL_PARAMS.ListRequestData ) => Promise<GOLBAL_PARAMS.ListData<GOLBAL_PARAMS.DictInfo>>
 * @props {boolean} isFocusLoad - 是否获取焦点加载数据
 * @props {boolean} immediate - 首次是否不加载
 * @props {boolean} id - 通过切换id获取下拉框
 *
 * @example
 * const getOptions = getSelectOptions<T>(info, Promise, (item) => ({
    label: item.name,
    value: item.id
  }))
 * <CustomSelect v-model:value="xxx" :getOptions="getOptions" />
 *
 * @author cx
 * @date 2024-01-18
 */
import { debounce, throttle, uniqBy, cloneDeep } from 'lodash'

type CustomSelectProps = {
  /* 下拉框的值 */
  value?: string | string[]
  /* 通过切换id获取下拉框 */
  id?: string | number
  /* 是否获取焦点加载数据 */
  isFocusLoad?: boolean
  /* 首次是否不加载 */
  immediate?: boolean
  /* 获取下拉框数据的接口 */
  getOptions: (
    info: GOLBAL_PARAMS.ListRequestData
  ) => Promise<GOLBAL_PARAMS.ListData<GOLBAL_PARAMS.DictInfo>>
}

/* 父组件参数 */
const props = defineProps<CustomSelectProps>()
/* 下拉框数据 */
const options = ref<GOLBAL_PARAMS.DictInfo[]>([])
/* 已选择的下拉数据 */
const selectOptions = ref<GOLBAL_PARAMS.DictInfo[]>([])
/* 是否第一次触发焦点 */
const isFirstFocus = ref<boolean>(true)
/* 分页 */
const page = reactive({
  pageNumber: 1,
  pageSize: 10
})
/* 加载 */
const loading = ref<boolean>(false)
/* 是否加载更多 */
const noMore = ref<boolean>(false)

/* 防抖查询 */
const search = debounce((key: string) => {
  // 处理多选情况，过滤已选择的下拉并存储
  console.log(props.value, 'props.value')

  selectOptions.value = props.value
    ? options.value.filter((item) => props.value?.includes(String(item.value)))
    : []
  options.value = []
  page.pageNumber = 1
  getOptions(key)
}, 300)

/* 节流滚动查询 */
const popupScroll = throttle(() => {
  if (loading.value || noMore.value) return
  page.pageNumber = page.pageNumber + 1
  getOptions()
}, 300)

/* 获取下拉框数据 */
const getOptions = async (name?: string) => {
  if (!props?.getOptions) return
  loading.value = true
  const { datas = [], total = 0 } = await props.getOptions({
    name,
    customSelectRid: props.id,
    ...toRaw(page)
  })

  const list = cloneDeep(toRaw(selectOptions.value))
  // 为了兼容多选时，已选择内容不展示title的问题，合并已选择的下拉内容和新list并去重
  if (page.pageNumber === 1) {
    // 第一次加载或者模糊查询时,查询时默认把查询到的放在最前面
    options.value = uniqBy([...datas, ...list], 'value')
  } else {
    // 滚动加载。滚动时把查询到的放在最后面
    options.value = uniqBy([...list, ...toRaw(options.value), ...datas], 'value')
  }
  loading.value = false
  // 是否加载更多
  if (options.value.length >= total) {
    noMore.value = true
  } else {
    noMore.value = false
  }
}

/* 监听id变化并且获取下拉框 */
watch(
  () => props.id,
  () => {
    if (props.id) {
      getOptions()
    } else {
      options.value = []
    }
  }
)

/* 获取焦点触发 */
const focus = () => {
  // 是否第一次触发焦点
  if (props.immediate && props.isFocusLoad && isFirstFocus.value) {
    isFirstFocus.value = false
    getOptions()
  }
}

// 初始化
onMounted(() => {
  if (!props.immediate) getOptions()
})

defineExpose({
  getOptions,
  options
})
</script>

<template>
  <a-select
    v-bind="$attrs"
    :value="value"
    :filter-option="false"
    style="min-width: 150px"
    showSearch
    @focus="focus()"
    @search="search"
    @popup-scroll="popupScroll"
    :options="options"
  >
    <template v-if="loading" #notFoundContent>
      <a-spin size="small" />
    </template>
  </a-select>
</template>
